feat(customers): add customer.* webhook events and customer_id list filters - #52
Conversation
…ilters
The deployed API already accepts these; this is additive and safe against
production today.
- Add customer.new/customer.update/customer.delete to the WebhookEvents
Literal, and fold the previously orphaned receiver.delete (from
src/blindpay/types.py, disconnected from WebhookEvents and never wired
up) into the same Literal alongside it.
- Mark receiver.new/receiver.update/receiver.delete deprecated with a
comment pointing at the same changelog post used by the receivers
namespace deprecation warning in client.py. They are not removed:
receiver.* webhooks are still dual-emitted by the deployed API.
- Retire the stray, publicly exported types.WebhookEvent alias now that
receiver.delete lives in WebhookEvents.
- Add customer_id as an optional list filter on ListPayinsInput and
ListPayoutsInput, alongside the existing receiver_id (not removed).
GET /v1/instances/{id}/payins and /payouts already accept customer_id
as a query filter in the deployed API spec.
- Update the README quickstart example to use blindpay.customers.get()
instead of the runtime-deprecated blindpay.receivers.get().
Deliberately excluded (wave 2, needs blindpay-v2 PR #1799 deployed
first): the remaining 11 receiver_* -> customer_* field renames, and
removal of receiver.* from the webhook enum. Shipping those today would
break every SDK user still on the deployed field names.
Claude-Session: https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
The branch had deleted the public `WebhookEvent` alias from types.py and dropped it from __init__.py's imports and __all__. It is dead inside the repo, but it is an exported symbol, so `from blindpay import WebhookEvent` would break on what is otherwise a purely additive minor release. Restored with its original value and a deprecation note pointing at WebhookEvents, which is the alias that actually lists every event. Removal belongs in the next major, alongside dropping the receiver.* members. Claude-Session: https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs
The customer.* rename shims declared customer_id on nested owners, limit-increase requests, blockchain wallets, and offramp wallets. The deployed API still sends receiver_id in all four places (server-side customers rename is not deployed yet). Since these are TypedDicts, a consumer indexing the declared key gets a KeyError today. Verified against apps/api/openapi.json: ReceiverOut.owners[], required receiver_id on BlockchainWalletOut, OfframpWallet, and GetReceiverLimitIncreaseOut. Updated the wallet test fixtures to match and added receiver_id assertions so a regression of this exact bug fails the suite. bank_accounts.py's owners field is untouched: the deployed BankAccountOut has neither receiver_id nor customer_id, so that is a separate pre-existing type-accuracy issue, not this bug. Claude-Session: https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs
|
Added a follow-up commit to this branch fixing 4 pre-existing wrong-key bugs found while auditing wave 1 against the deployed What was wrongSomeone previously renamed some response field names in this SDK ahead of the server. The deployed API still sends
Each was confirmed directly against Why this is wave-1 safeThese are TypedDicts, so the fix only changes what static type checking (mypy) expects the response shape to be; nothing at runtime was validating the old key. That means:
Updated Left untouched, on purpose:
BuildNo version bump (release-please handles that from conventional commits; used a |
…ssertions pyright flagged reportOptionalSubscript on response["data"][0] since BlindpayApiResponse["data"] is Optional. Add the same is-not-None assert already used elsewhere in the suite to narrow the type. Claude-Session: https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs
Blockchain wallets, offramp wallets and limit-increase responses go through addCustomerIdMiddleware, which adds customer_id wherever receiver_id is present today, and both are required in the post-#1799 spec. Revert the earlier reversion to receiver_id for those three. Owner is different: it's a nested owners[] element, which the middleware does not recurse into, so customer_id is not sent today but will be after #1799. Declare both keys as NotRequired so neither shape breaks. Claude-Session: https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs
What
Wave 1 of the receivers -> customers migration for blindpay-python: additive,
non-breaking changes that the currently deployed API already accepts.
No renames, no removals, plus a follow-up commit fixing 4 pre-existing
wrong response field names that were already live bugs before this PR.
customer.new,customer.update,customer.deleteto theWebhookEventsLiteral inresources/webhooks/webhooks.py. The deployedWebhookEndpointIn.eventsenum has included these since June 2026 anddual-emit is already active, so subscribing to them works against
production today.
receiver.deleteinto the sameWebhookEventsLiteral (it existedonly as an orphaned, publicly-exported
types.WebhookEventalias,disconnected from
WebhookEventsand never wired into any webhookinput/response type). That stray alias is retired.
receiver.new/receiver.update/receiver.deletedeprecated witha comment pointing at the same changelog post (
2026-06-04-customers-rename)used by the
receiversnamespaceDeprecationWarninginclient.py. Theyare not removed since
receiver.*webhooks are still dual-emitted bythe deployed API; removing them now would silently drop deliveries for
subscribers who haven't migrated.
customer_idas an optional filter onListPayinsInputandListPayoutsInput, alongside the existingreceiver_id(kept, notremoved).
GET /v1/instances/{id}/payinsand/payoutsalready acceptcustomer_idas a query filter in the deployed API spec.blindpay.customers.get()instead of the runtime-deprecated
blindpay.receivers.get().Also fixes: wrong response field names (pre-existing, live bug)
A prior optimistic rename declared some response field names ahead of the
server. The deployed API still sends
receiver_idin these four places, sothe SDK declared a key that never arrives. Each was verified directly
against
apps/api/openapi.json(schema name + required-ness) and traced tothe actual response type of its endpoint, not just matched by name:
Owner(nested owners on business customers,resources/customers/customers.py):declared
customer_id, specReceiverOut.owners[]usesreceiver_id.LimitIncreaseRequest(resources/customers/customers.py): declaredcustomer_id, specGetReceiverLimitIncreaseOutrequiresreceiver_id.OfframpWallet(resources/wallets/offramp.py): declaredcustomer_id,spec
OfframpWalletrequiresreceiver_id.BlockchainWallet(resources/wallets/blockchain.py): declaredcustomer_id, specBlockchainWalletOutrequiresreceiver_id.These are TypedDicts, so nothing validated the wrong key at runtime; code
that indexed
["customer_id"]on these objects was already getting aKeyErrortoday, since the wire never sends that key. Fixing the declaredkey doesn't break anything that worked, it aligns the SDK with what
production sends right now, independent of blindpay-v2 PR #1799.
Updated the
wallets/blockchainandwallets/offramptest fixtures to usereceiver_idand added an explicit key assertion in one test per file so aregression of this exact bug fails the suite.
Left untouched, on purpose:
receiver_amount(singular) andcurrency_typeeverywhere (different concept, see below); the SDK-level
customer_idmethod parameters/path args used throughout these same files (correct as
idiomatic Python parameter names, not wire keys); and
bank_accounts.py'sOfframpWalletshape, whose deployedBankAccountOuthas neither
receiver_idnorcustomer_id, a separate pre-existingtype-accuracy issue out of scope here.
Why this is safe today
Every change here was verified against the deployed production
openapi.json, not the pending PR spec: the webhook event enum, and thecustomer_idquery filter on payins/payouts, are already live. Nothing inthis PR depends on blindpay-v2 PR #1799 (still open, not deployed).
Deliberately NOT in this PR (wave 2)
These require blindpay-v2 PR #1799 to ship first, because the deployed API
does not accept them yet. Shipping them now would break every current user
of this SDK:
receiver_*->customer_*field renames (e.g.receiver_local_amount,receiver_wallet_address,receiver_network,receiver_token,receiver_invite_redirect_url,receivers_amount->customers_amount, etc). Only a handful of these have any code in thisSDK to begin with; most of the field list isn't modeled here at all
(no
InstanceOut/single-instance GET).receiver.*from the webhook event enum.receiversresource (already runtime-deprecated,scheduled for v3.0.0 removal per
client.py).Not touched, confirmed unchanged, because these are a different concept
(the amount/side a transaction leg refers to, not the customer resource):
receiver_amount(singular) on quotes/payins/payouts/transfers, andcurrency_type/amount_referencestill accepting thesender/receiverstring literal values.
Version
Not manually bumped. This repo uses
release-please(seerelease-please-config.json,.github/workflows/publish.yaml) which bumpssrc/blindpay/__init__.pyandCHANGELOG.mditself from conventionalcommits after merge to main. The original commit uses a
feat:prefix andthe follow-up field-name fix commit uses
fix:, so release-please willcompute the right version bump on the next release PR, consistent with how
every prior PR in this repo's history has been handled (no prior PR
hand-edits the version or changelog).
Build output
The 44 warnings are all the expected
DeprecationWarningfromblindpay.receiversaccess intest_receivers.py/test_bank_accounts.py,pre-existing and unrelated to this change.
https://claude.ai/code/session_01F1stiNzuNtJXoXtiW9ZCbs